/*
 * Copyright (c)  2015~2020, xforceplus
 * All rights reserved.
 * Project:datacare-orm
 * Id: Query.java   2020-09-28 23-05-26
 * Author: Evan
 */
package cn.datacare.orm.query;

import cn.datacare.orm.database.ColDesc;
import cn.datacare.orm.database.TableDesc;
import cn.datacare.orm.engine.SQLParameter;
import cn.datacare.orm.exception.OrmCoreException;
import cn.datacare.orm.manager.impl.SQLManager;
import cn.datacare.orm.query.error.QueryError;
import cn.datacare.orm.query.group.GroupBy;
import cn.datacare.orm.query.order.OrderBy;
import cn.datacare.orm.source.SQLReady;
import cn.datacare.orm.source.SQLSource;
import cn.datacare.orm.type.JavaType;
import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.beetl.core.resource.StringTemplateResourceLoader;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static cn.datacare.orm.constant.Constants.PARAM_PARAMS;
import static cn.datacare.orm.constant.Constants.PARAM_ROOT;
import static cn.datacare.orm.query.error.QueryError.QUERY_CONDITION_ERROR_UPDATE_NO_DATA;
import static cn.datacare.orm.util.Separator.COMMA;
import static cn.datacare.orm.util.Separator.SPACE;

/**
 * <p>
 * Title:
 * </p>
 * <p>
 * Description:
 * </p>
 * <p>
 * Copyright: 2015~2020
 * </p>
 * <p>
 * Company/Department: xforceplus
 * </p>
 *
 * @author Evan
 * <b>Creation Time:</b> 2020-09-28 23-05-26
 * @since V1.0
 */
public class QueryImpl<T extends StringQuery> extends QueryConditionImpl<T> implements Query<T>{
    /**
     * *
     */
    private static final String ALL_COLUMNS="*";
    /**
     * 泛型类型
     */
    private Class<T> clazz;

    /***
     * 模板消息
     */
    StringTemplateResourceLoader tempLoader = new StringTemplateResourceLoader();


    public QueryImpl(SQLManager sqlManager,Class<T> clazz){
        this.sqlManager=sqlManager;
        this.clazz=clazz;
    }



    /***
     * 获取一个新的条件
     * @return Query<T>
     */
    @Override
    public Query<T> condition() {
        return new QueryImpl<>(this.sqlManager,clazz);
    }


    @Override
    public List<T> select(String... columns) {
        return this.selectByType(clazz, columns);
    }

    @Override
    public List<T> select() {
        return this.selectByType(clazz);
    }



    @Override
    public List<T> selectSimple() {
        return selectByType(clazz, getSimpleColumns());
    }

    @Override
    public T single(String... columns) {
        List<T> list = this.limit(getFirstRowNumber(), 1).select(columns);
        if (list.isEmpty()) {
            return null;
        }
        // 同SQLManager.single 一致，只取第一条。
        return list.get(0);
    }

    @Override
    public Map mapSingle(String... columns) {
        List<Map> list = this.limit(getFirstRowNumber(), 1).selectByType(Map.class, columns);
        if (list.isEmpty()) {
            return null;
        }
        // 同SQLManager.single 一致，只取第一条
        return list.get(0);
    }

    @Override
    public T singleSimple() {
        return single(getSimpleColumns());
    }

    @Override
    public T uniqueSimple() {
        return unique(getSimpleColumns());
    }

    @Override
    public T unique(String... cols) {
        List<T> list = this.limit(getFirstRowNumber(), 2).select(cols);
        if (list.isEmpty()) {
            throw new OrmCoreException(QueryError.UNIQUE_EXCEPT_ERROR_NO_DATA);
        } else if (list.size() != 1) {
            throw new OrmCoreException(QueryError.UNIQUE_EXCEPT_ERROR_MANY_DATA);
        }
        return list.get(0);
    }

    private int getFirstRowNumber() {
        return this.sqlManager.isOffsetStartZero() ? 0 : 1;
    }

    @Override
    public <K> List<K> select(Class<K> retType, String... columns) {
        return this.selectByType(retType, columns);
    }

    @Override
    public List<Map> mapSelect(String... columns) {
        return this.selectByType(Map.class, columns);
    }

    /**
     *
     * @param clazz
     * @param columns
     * @param <K>
     * @return
     */
    @Override
    public <K> List<K> selectByType(Class<K> clazz, String... columns) {
        String column = splicingColumns(columns).toString();
        if(distinct){
            column =" DISTINCT "+column;
        }
        StringBuilder sql = assembleSelectSql(column);
        String targetSql = sql.toString();
        Object[] paras = getParams().toArray();
        return  this.sqlManager.execute(new SQLReady(targetSql, paras), clazz);
    }

    /***
     * 组装查询的sql语句
     * @return
     */
    private StringBuilder assembleSelectSql(String column) {
        StringBuilder sb = new StringBuilder("SELECT ").append(column).append(SPACE);
        sb.append("FROM ").append(getTableName(clazz)).append(SPACE).append(getSql());
        sb = addAdditionalPartSql(sb);
        return sb;
    }

    /**
     * 增加分页，分组排序
     */
    private StringBuilder addAdditionalPartSql(StringBuilder sql) {
        addGroupAndOrderPartSql(sql);
        // 增加翻页
        if (this.startRow != -1) {
            sql = new StringBuilder(sqlManager.getDialectType().getPageSQLStatement(sql.toString(), startRow, pageSize));
        }
        return sql;
    }

    /**
     * 增加分组，排序
     */
    private void addGroupAndOrderPartSql(StringBuilder sql) {
        if (this.orderBy != null && this.groupBy != null) {
            //先group by 后 order by 顺序
            sql.append(groupBy.getGroupBy()).append(SPACE);
            sql.append(orderBy.getOrderBy()).append(SPACE);
        } else if (this.orderBy != null) {
            sql.append(orderBy.getOrderBy()).append(SPACE);
        } else if (this.groupBy != null) {
            sql.append(groupBy.getGroupBy()).append(SPACE);
        }
    }

    @Override
    public int update(Object t) {
        SQLSource sqlSource = this.sqlManager.getDialectType().genUpdateAbsolute(clazz);
        return handlerUpdateSql(t, sqlSource);
    }

    @Override
    public int updateSelective(Object t) {
        SQLSource sqlSource = this.sqlManager.getDialectType().genUpdateAll(clazz);
        return handlerUpdateSql(t, sqlSource);
    }

    private int handlerUpdateSql(Object t, SQLSource sqlSource) {
        if (this.sql == null || this.sql.length() == 0) {
            throw new OrmCoreException(QUERY_CONDITION_ERROR_UPDATE_NO_DATA);
        }

        GroupTemplate gt = this.sqlManager.getBeetl().getGroupTemplate();
        Template template = gt.getTemplate(sqlSource.getTemplate(), this.tempLoader);
        template.binding(PARAM_PARAMS, new ArrayList<>());
        template.binding(PARAM_ROOT, t);
        String sql = template.render();
        List<SQLParameter> param = (List<SQLParameter>) template.getCtx().getGlobal(PARAM_PARAMS);
        List<Object> paraLis = new ArrayList<>();
        for (SQLParameter sqlParameter : param) {
            paraLis.add(sqlParameter.getValue());
        }

        addPreParam(paraLis);

        StringBuilder sb = new StringBuilder(sql);

        sb.append(SPACE).append(getSql());
        String targetSql = sb.toString();
        Object[] paras = paraLis.toArray();
        int row = this.sqlManager.executeUpdate(new SQLReady(targetSql, paras));
        return row;
    }

    @Override
    public int insert(T t) {
        int ret = this.sqlManager.insert(t, true);
        return ret;
    }

    @Override
    public int insertSelective(T t) {
        return this.sqlManager.insertTemplate(t, true);
    }

    @Override
    public int delete() {
        StringBuilder sb = new StringBuilder("DELETE FROM ");
        sb.append(getTableName(clazz)).append(" ").append(getSql());
        String targetSql = sb.toString();
        Object[] paras = getParams().toArray();
        int row = this.sqlManager.executeUpdate(new SQLReady(targetSql, paras));
        return row;
    }

    @Override
    public long count() {
        StringBuilder sb = new StringBuilder("SELECT COUNT(1) FROM ");
        sb.append(getTableName(clazz)).append(" ").append(getSql());
        String targetSql = sb.toString();
        Object[] paras = getParams().toArray();
        List results = this.sqlManager.execute(new SQLReady(targetSql, paras), Long.class);
        return (Long) results.get(0);
    }

    @Override
    public Query<T> having(QueryCondition condition) {
        // 去除叠加条件中的WHERE
        int i = condition.getSql().indexOf(WHERE);
        if (i > -1) {
            condition.getSql().delete(i, i + 5);
        }
        if (this.groupBy == null) {
            throw new OrmCoreException(QueryError.QUERY_SQL_ERROR, getSqlErrorTip("having 需要在groupBy后调用"));
        }

        groupBy.addHaving(condition.getSql().toString());
        this.addParam(condition.getParams());
        return this;
    }

    @Override
    public Query<T> groupBy(String column) {
        GroupBy groupBy = getGroupBy();
        groupBy.add(getCol(column));
        return this;
    }

    @Override
    public Query<T> orderBy(String orderBy) {
        OrderBy orderByInfo = this.getOrderBy();
        orderByInfo.add(orderBy);
        return this;
    }

    @Override
    public Query<T> asc(String column) {
        OrderBy orderByInfo = this.getOrderBy();
        orderByInfo.add(getCol(column) + " ASC");
        return this;
    }

    @Override
    public Query<T> desc(String column) {
        OrderBy orderByInfo = this.getOrderBy();
        orderByInfo.add(getCol(column) + " DESC");
        return this;
    }

    /**
     * Order by
     * @return OrderBy
     */
    private OrderBy getOrderBy() {
        if (this.orderBy == null) {
            orderBy = new OrderBy();
        }
        return this.orderBy;
    }

    /**
     * group by
     * @return GroupBy
     */
    private GroupBy getGroupBy() {
        if (this.groupBy == null) {
            groupBy = new GroupBy();
        }
        return this.groupBy;
    }

    /**
     * 默认从1开始，自动翻译成数据库的起始位置。如果配置了OFFSET_START_ZERO =true，则从0开始。
     */
    @Override
    public Query<T> limit(long startRow, long pageSize) {
        this.startRow = startRow;
        this.pageSize = pageSize;
        return this;

    }

    protected <K> PageQuery<K> pageByType(long pageNumber, long pageSize, Class<K> clazz, String... columns) {
        StringBuilder columnStr = splicingColumns(columns);
        //此处查询语句不需要设置分页
        this.startRow = -1;
        StringBuilder sql = assembleSelectSql(columnStr.toString());
        //检测是否包含groupBy
        if (this.groupBy != null) {
            sql = new StringBuilder("SELECT * FROM (").append(sql).append(") t");
        }
        String targetSql = sql.toString();
        Object[] paras = getParams().toArray();
        SQLReady sqlReady = new SQLReady(targetSql, paras);
        PageQuery<K> pageQuery = new PageQuery<>(pageNumber, pageSize);
        return this.sqlManager.execute(sqlReady, clazz, pageQuery);
    }


    @Override
    public PageQuery<T> page(long pageNumber, long pageSize, String... columns) {
        return pageByType(pageNumber, pageSize, clazz, columns);
    }

    @Override
    public PageQuery<T> pageSimple(long pageNumber, long pageSize) {
        return page(pageNumber, pageSize, getSimpleColumns());
    }

    @Override
    public <K> PageQuery<K> page(long pageNumber, long pageSize, Class<K> retType, String... columns) {
        return pageByType(pageNumber, pageSize, retType, columns);
    }

    @Override
    public PageQuery<Map> mapPage(long pageNumber, long pageSize, String... columns) {
        return pageByType(pageNumber, pageSize, Map.class, columns);
    }


    /***
     * 获取错误提示
     *
     * @return
     */
    private String getSqlErrorTip(String couse) {
        return String.format("\n┏━━━━━ SQL语法错误:\n" + "┣SQL：%s\n" + "┣原因：%s\n" + "┣解决办法：您可能需要重新获取一个Query\n" + "┗━━━━━\n",
                getSql().toString(), couse);
    }

    /***
     * 获取错误提示
     *
     * @return
     */
    private String getSqlErrorTip(String couse, String solve) {
        return String.format("\n┏━━━━━ SQL语法错误:\n" + "┣SQL：%s\n" + "┣原因：%s\n" + "┣解决办法：" + solve + "\n" + "┗━━━━━\n",
                getSql().toString(), couse);
    }

    /***
     * 获取简要字段
     * @return
     */
    private String[] getSimpleColumns() {
        String tableName =this.sqlManager.getNameConversion().getTableName(this.clazz);
        TableDesc tableDesc =this.sqlManager.getMetaDataManager().getTable(tableName);
        Map<String, ColDesc> colMap = tableDesc.getColsDetail();
        List<String> cols = new ArrayList<>(colMap.size());
        for(Map.Entry<String,ColDesc> entry:colMap.entrySet()){
            int sqlType = entry.getValue().getSqlType();
            if(!JavaType.isBigType(sqlType)){
                cols.add(entry.getKey());
            }
        }
        String[] columns = new String[cols.size()];
        cols.toArray(columns);
        return columns;
    }

    /**
     * 拼接字段，不传参数时为*
     *
     * @param columns
     * @return
     */
    private StringBuilder splicingColumns(String[] columns) {
        if (columns == null || columns.length < 1) {
            return new StringBuilder(ALL_COLUMNS);
        }
        StringBuilder columnStr = new StringBuilder();
        for (String column : columns) {
            columnStr.append(getColTrunk(column)).append(COMMA);
        }
        columnStr.deleteCharAt(columnStr.length() - 1);
        return columnStr;
    }
}
